package com.gobang.game;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.gobang.GoBangApplication;
import com.gobang.model.User;
import com.gobang.model.UserMapper;

import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;

import java.io.IOException;
import java.util.UUID;

public class Room {
    private static final int MAX_ROW = 15;
    private static final int MAX_COL = 15;
    private String roomId;
    private User user1;
    private User user2;
    private int WhiteUserId = 0; //先手的ID
    // 棋盘, 数字 0 表⽰未落⼦位置. 数字 1 表⽰玩家 1 的落⼦. 数字 2 表⽰玩家 2 的落⼦
    //注意区分：客户端落子状态只有两种，落子/未落字；服务器端三个状态，未落子/玩家1落子/玩家二落子
    //判定胜负关系，一般还是由服务端来判定，如果放在客户端一旦被篡改影响公平性（外挂）
    //外挂的工作流程主要是篡改客户端数据和逻辑
    private int[][] chessBoard = new int[MAX_ROW][MAX_COL];
    private ObjectMapper objectMapper = new ObjectMapper();
    // @Autowired
    private OnlineUserManager onlineUserManager;
    //Room 类内部要在游戏结束的时候销毁房间, 需要⽤到 RoomManager
    //@Autowired
    private RoomManager roomManager;

    // @Autowired
    //Room 类内部要修改玩家的分数, 需要⽤到 UserMapper
    private UserMapper userMapper;

    //通过这个方法来处理一次落子操作
    public void putChess(String reqJson) throws IOException {
        //1.记录当前落子位置
        GameRequest request = objectMapper.readValue(reqJson,GameRequest.class);
        GameResponse response = new GameResponse();
        //当前这个子是玩家一放的还是玩家二放的，来决定数组中放谁的
        int chess  = request.getUserId() == user1.getUserId() ? 1 : 2;
        int row  = request.getRow();
        int col = request.getCol();
        if(chessBoard[row][col] != 0){
            //在客户端已经针对重复落子进行过判定了
            System.out.println("落子位置已经有子" + request);
            return;
        }
        chessBoard[row][col] = chess;
        printChessBoard();
        //2.进行胜负判定
        int winner = checkWinner(row,col,chess);

        //3.给房间中的客户端都返回反应
        response.setMessage("putChess");
        response.setUserId(request.getUserId());
        response.setRow(row);
        response.setCol(col);
        response.setWinner(winner);

        //要想给用户发送websocket数据，就需要获取到这个用户的websocketSession
        WebSocketSession session1 = onlineUserManager.getFromGameRoom(user1.getUserId());
        WebSocketSession session2 = onlineUserManager.getFromGameRoom(user2.getUserId());
        //万一当前查到的绘画为空，说明玩家线
        if(session1 == null){
            response.setWinner(user2.getUserId());
            System.out.println("玩家1 掉线");
        }
        if(session2 == null){
            response.setWinner(user1.getUserId());
            System.out.println("玩家2 掉线");
        }
        //把响应构造成JSON字符串，通过session进行传输
        String respJson = objectMapper.writeValueAsString(response);
        if(session1 != null){
            session1.sendMessage(new TextMessage(respJson));
        }
        if(session2 != null){
            session2.sendMessage(new TextMessage(respJson));
        }
        //如果当前胜负已分，应该销毁房间
        if(response.getWinner() != 0){
            userMapper.userWin(response.getWinner() == user1.getUserId() ? user1 : user2);
            userMapper.userLose(response.getWinner() == user1.getUserId() ? user2 : user1);
            roomManager.remove(roomId, user1.getUserId(), user2.getUserId());
            System.out.println("游戏结束, 房间已经销毁! roomId: " + roomId + "获胜⽅为:" + response.getWinner());

        }
    }
    private void printChessBoard() {
        System.out.println("打印棋盘信息: ");
        System.out.println("===========================");
        for (int r = 0; r < MAX_ROW; r++) {
            for (int c = 0; c < MAX_COL; c++) {
                System.out.print(chessBoard[r][c] + " ");
            }
            System.out.println();
        }
        System.out.println("===========================");
    }

    //如果玩家1获胜，就返回玩家一的id；如果玩家二获胜，就返回玩家二的id；要是胜负未分，就返回一个0
    private int checkWinner(int row,int col,int chess){
        //1.检查所有的行
        //遍历所有的情况
        //针对其中的一种情况，来判定这五个子是不是在一起了
        //不光是这5个子连着了，而且还带和玩家的落子是一样的才能获胜
        for (int c = col - 4; c <= col; c++) {
           //for中是找最左落子位置，从最左边来数
            try {
                //1.横着1 1 1 1 1<--当前子
                if (chessBoard[row][c] == chess && chessBoard[row][c + 1] == chess
                        && chessBoard[row][c + 2] == chess && chessBoard[row][c + 3] == chess
                        && chessBoard[row][c + 4] == chess) {
                    return chess == 1 ? user1.getUserId() : user2.getUserId();
                }
            }catch(ArrayIndexOutOfBoundsException e) {
                //考虑下标越界,如果出现下标越界的情况，就在这里直接忽略这个异常。
                continue;
                //相当于这个意思
                //if (c < 0 || c >= MAX_COL)
                //    continue;

            }
        }
        // 2. 检查所有的列(循环五次)
        for (int r = row - 4; r <= row; r++) {
            //for中是找最上落子位置，从最上边往下来数
            if (r < 0 || r >= MAX_ROW) {
                continue;
            }
            if (chessBoard[r][col] == chess
                    && chessBoard[r + 1][col] == chess
                    && chessBoard[r + 2][col] == chess
                    && chessBoard[r + 3][col] == chess
                    && chessBoard[r + 4][col] == chess) {
                return chess == 1 ? user1.getUserId() : user2.getUserId();
            }
        }
        // 3. 检查左对⻆线
        for (int r = row - 4, c = col - 4; r <= row && c <= col; r++, c++) {
            //for中是找最左上落子位置，从最左上边往右下来数
            if (r < 0 || r >= MAX_ROW || c < 0 || c >= MAX_COL) {
                continue;
            }
            if (chessBoard[r][c] == chess
                    && chessBoard[r + 1][c + 1] == chess
                    && chessBoard[r + 2][c + 2] == chess
                    && chessBoard[r + 3][c + 3] == chess
                    && chessBoard[r + 4][c + 4] == chess) {
                return chess == 1 ? user1.getUserId() : user2.getUserId();
            }
        }
        // 4. 检查右对⻆线
        for (int r = row - 4, c = col + 4; r <= row && c >= col; r++, c--) {
            ////for中是找最右上落子位置，从最右上边往左下来数
            if (r < 0 || r >= MAX_ROW || c < 0 || c >= MAX_COL) {
                continue;
            }
            if (chessBoard[r][c] == chess
                    && chessBoard[r + 1][c - 1] == chess
                    && chessBoard[r + 2][c - 2] == chess
                    && chessBoard[r + 3][c - 3] == chess
                    && chessBoard[r + 4][c - 4] == chess) {
                return chess == 1 ? user1.getUserId() : user2.getUserId();
            }
        }
        return 0;
    }
    public Room(){
        //构造Room的时候生成一个唯一的字符串表示房间id
        //使用UUID来作房间的id
        roomId = UUID.randomUUID().toString();
        //使用@Autowired的前提是，使用这个注解的类本身也是一个spring组件,被spring扫描到才能使用
        //Room不能加上，因为这样Room成为了单例，但是不符合设定，因为有许多Room实例，不可能是单例
        //Spring也支持多例，但是Room已经在RoomManager中被我们手动管理起来了，因此Room不能设为Spring的组件
        //但是又希望能从Spring中获取onlineUserManager，roomManager userMapper，因此采用手动
        //通过入口类中记录的contexct来手动记录前面的RoomManager和onlineUserManager
        //这些属性不能通过自动注入的形式进行初始化，因为房间room会有多个，不能使用@Autowired注入以下属性。
        onlineUserManager = GoBangApplication.context.getBean(OnlineUserManager.class);
        roomManager = GoBangApplication.context.getBean(RoomManager.class);
        userMapper = GoBangApplication.context.getBean(UserMapper.class);
    }
    public static void main(String args){
        Room room = new Room();
        System.out.println(room.roomId);
    }

    public String getRoomId() {
        return roomId;
    }

    public void setRoomId(String roomId) {
        this.roomId = roomId;
    }

    public User getUser1() {
        return user1;
    }

    public void setUser1(User user1) {
        this.user1 = user1;
    }

    public User getUser2() {
        return user2;
    }

    public void setUser2(User user2) {
        this.user2 = user2;
    }

    public int getWhiteUserId() {
        return WhiteUserId;
    }

    public void setWhiteUserId(int whiteUserId) {
        WhiteUserId = whiteUserId;
    }

}
